﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using SystemImaging = System.Windows.Media.Imaging;
using iNet.Collections;
using iNet.Media.Imaging.Png;
using iNet.Media.Xmp;
using iNet.Tasks;

namespace iNet.Media.Imaging
{
	/// <summary>
	/// PNG 影像編碼工作。
	/// </summary>
	public class PngImageEncodeTask : BasicImageEncodeTask
	{
		#region 欄位
		static uint[] _CrcTable;
		#endregion
		#region 型別建構子
		static PngImageEncodeTask()
		{
			//建立 CRC 對照表
			_CrcTable = new uint[256];
			for (int i = 255; i >= 0; i--)
			{
				uint n = (uint)i;
				for (int b = 8; b > 0; b--)
				{
					if ((n & 1) == 0)
						n >>= 1;
					else
						n = 0xEDB88320U ^ (n >> 1);
				}
				_CrcTable[i] = n;
			}
		}
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 PngImageEncodeTask 執行個體。
		/// </summary>
		public PngImageEncodeTask()
			: base(ImageFormats.Png)
		{ }
		/// <summary>
		/// 初始化新的 PngImageEncodeTask 執行個體。
		/// </summary>
		/// <param name="destination">儲存編碼結果的媒體儲存。</param>
		public PngImageEncodeTask(MediaStorage destination)
			: this()
		{
			this.Destination = destination;
		}
		#endregion
		#region CreateChunk
		byte[] CreateChunk(PngChunkType type, byte[] data)
		{
			//建立緩衝區
			int dataLength = (data == null ? 0 : data.Length);
			byte[] chunk = new byte[dataLength + 12];

			//建立 Chunk
			BitConverter converter = BitConverter.BigEndian;
			converter.GetBytes(dataLength).CopyTo(chunk, 0);
			converter.GetBytes((uint)type).CopyTo(chunk, 4);
			if (dataLength > 0)
				data.CopyTo(chunk, 8);
			converter.GetBytes(this.GetCRC(0xFFFFFFFFU, chunk, 4, dataLength + 4)).CopyTo(chunk, 8 + dataLength);

			//回傳
			return chunk;
		}
		#endregion
		#region CreateCompatibleMetadata
		/// <summary>
		/// 建立與此媒體格式相容且可編輯的中繼資料。
		/// </summary>
		/// <param name="template">現有的中繼資料。</param>
		/// <returns>與此媒體格式相容的中繼資料。</returns>
		public override IMetadata CreateCompatibleMetadata(IMetadata template)
		{
			PngMetadata metadata = (template as PngMetadata);
			if (metadata == null || metadata.IsReadOnly)
				return new PngMetadata(template, false);
			return metadata;
		}
		#endregion
		#region CreateEncoder
		internal override SystemImaging.BitmapEncoder CreateEncoder()
		{
			return new SystemImaging.PngBitmapEncoder();
		}
		#endregion
		#region Encode
		protected override bool Encode(TaskVersion version, MediaStorage destination, MediaEncodeRequests mediaEncodeReqs, ImageEncodeRequests imageEncodeReqs)
		{
			//編碼影像
			if (!base.Encode(version, destination, mediaEncodeReqs, imageEncodeReqs))
				return false;

			//中斷確認
			if (this.IsCanceling)
				return true;

			//編碼中繼資料
			if ((mediaEncodeReqs & Media.MediaEncodeRequests.EncodeMetadata) != 0)
			{
				//取得中繼資料
				IMetadata metadata = this.EncodingMetadata;
				if (metadata == null && this.EncodingImage != null)
					metadata = this.EncodingImage.Metadata;

				//編碼中繼資料
				if (metadata != null)
				{
					Stream stream;
					if (!this.GetMediaStorageStream(version, destination, 0, out stream))
						throw new MediaEncodeFailedException(MediaEncodeFailedReason.CannotAccessStream);
					try
					{
						this.EncodeMetadata(stream, metadata);
						if (!this.CommitToMediaStorage(version, destination, 0, stream))
							return false;
					}
					finally
					{
						this.ReleaseMediaStorageStream(version, destination, stream);
					}
				}
			}

			//完成
			return true;
		}
		#endregion
		#region EncodeMetadata
		void EncodeMetadata(Stream stream, IMetadata metadata)
		{
			//取得起始位置
			long headerPosition = stream.Position;

			//確認格式
			byte[] buffer4 = new byte[4];
			byte[] buffer8 = new byte[8];
			BitConverter converter = BitConverter.BigEndian;
			if (stream.Read(buffer8, 0, 8) < 8 || converter.ToUInt32(buffer8, 0) != 0x89504E47 || converter.ToUInt32(buffer8, 4) != 0x0D0A1A0A)
				throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);

			//取出所有 chunk
			List<byte[]> chunks = new List<byte[]>();
			List<byte[]> dataChunks = new List<byte[]>();
			while (stream.Position < stream.Length)
			{
				//取得長度
				if (stream.Read(buffer4, 0, 4) < 4)
					throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);
				int length = converter.ToInt32(buffer4, 0);
				if (length < 0)
					throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);
				byte[] buffer = new byte[length + 12];
				buffer4.CopyTo(buffer, 0);

				//取得類型
				if (stream.Read(buffer, 4, 4) < 4)
					throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);

				//讀取資料
				PngChunkType type = (PngChunkType)converter.ToUInt32(buffer, 4);
				if (type == PngChunkType.IEND)
					break;
				else
				{
					switch ((PngChunkType)converter.ToUInt32(buffer, 4))
					{
						case PngChunkType.iTXt:
						case PngChunkType.tEXt:
						case PngChunkType.zTXt:
							stream.Seek(length + 4, SeekOrigin.Current);
							continue;
						case PngChunkType.IDAT:
							if (stream.Read(buffer, 8, length + 4) < length + 4)
								throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);
							dataChunks.Add(buffer);
							break;
						default:
							if (stream.Read(buffer, 8, length + 4) < length + 4)
								throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);
							chunks.Add(buffer);
							break;
					}
				}
			}

			//建立 chunk 及分離出 XMP 中繼資料
			byte[] chunk;
			List<XmpProperty> xmpProperties = new List<XmpProperty>();
			/*
			foreach (MetadataItem item in metadata)
			{
				if (item is XmpMetadataItem)
					xmpProperties.Add(item.RawData as XmpProperty);
				else if (item is PngTextualMetadataItem)
				{
					if (item.Name != "XML:com.adobe.xmp")
					{
						byte[] keyword = Encoding.Default.GetBytes(item.Name);
						byte[] value = Encoding.Default.GetBytes(item.RawData as string);
						byte[] data = new byte[keyword.Length + 1 + value.Length];
						keyword.CopyTo(data, 0);
						value.CopyTo(data, keyword.Length + 1);
						chunks.Add(this.CreateChunk(PngChunkType.tEXt, data));
					}
				}
			}
			*/

			//建立 XMP Packet
			if (xmpProperties.Count > 0)
			{
				//建立 XMP Packet
				byte[] xmpPacket = (new XmpWriter()).Write(xmpProperties);

				//建立 Chunk
				byte[] data = new byte[xmpPacket.Length + 22];
				Encoding.Default.GetBytes("XML:com.adobe.xmp").CopyTo(data, 0);
				xmpPacket.CopyTo(data, 22);
				chunk = this.CreateChunk(PngChunkType.iTXt, data);

				//加入集合
				chunks.Add(chunk);
			}

			//寫入標頭
			stream.Position = headerPosition;
			stream.Write(converter.GetBytes(0x89504E47U), 0, 4);
			stream.Write(converter.GetBytes(0x0D0A1A0AU), 0, 4);

			//寫入 Chunk
			foreach (byte[] c in chunks)
				stream.Write(c, 0, c.Length);
			foreach (byte[] c in dataChunks)
				stream.Write(c, 0, c.Length);

			//寫入結束標記
			chunk = new byte[] { 0, 0, 0, 0, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 };
			stream.Write(chunk, 0, 12);

			//設定資料流長度
			stream.SetLength(stream.Position);
		}
		#endregion
		#region GetCRC
		uint GetCRC(uint crc, byte[] data, int index, int length)
		{
			//計算 CRC
			for (int end = index + length; index < end; index++)
				crc = _CrcTable[(crc ^ data[index]) & 0xFF] ^ (crc >> 8);

			//回傳
			return ~crc;
		}
		#endregion
	}

	/// <summary>
	/// PNG 影像編碼工作工廠。
	/// </summary>
	public class PngImageEncodeTaskFactory : ImageEncodeTaskFactory
	{
		#region 建構子
		/// <summary>
		/// 初始化新的 PngImageEncodeTaskFactory 執行個體。
		/// </summary>
		public PngImageEncodeTaskFactory()
			: base("PNG Image EncodeTask Factory", ImageFormats.Png)
		{ }
		#endregion
		#region CreateEncodeTask
		/// <summary>
		/// 建立影像編碼工作。
		/// </summary>
		/// <param name="destination">儲存編碼結果的媒體儲存。</param>
		/// <returns>影像編碼工作。</returns>
		public override MediaEncodeTask CreateEncodeTask(MediaStorage destination)
		{
			return new PngImageEncodeTask(destination);
		}
		#endregion
	}
}
